using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Xml.XPath;

namespace FWF.Xml
{
    /// <summary>
    /// A library of basic Xml functions.
    /// </summary>
    public static class XmlLib
    {
        /// <summary>
        /// 
        /// </summary>
        public const string XmlPrefix = "igt";

        /// <summary>
        /// 
        /// </summary>
        public const string XmlNamespace = "urn:IGT.Casinolink";

        /// <summary>
        /// An xml prefix used for any IGT elements.
        /// </summary>
        public const string IGTPrefix = "igt";

        /// <summary>
        /// An xml prefix used for any S2S elements.
        /// </summary>
        public const string S2SPrefix = "s2s";

        /// <summary>
        /// An xml prefix used for any G2S elements.
        /// </summary>
        public const string G2SPrefix = "g2s";

        /// <summary>
        /// 
        /// </summary>
        public const string XlatPrefix = "xlat";

        /// <summary>
        /// The root xml namespace used in the G2S protocol.
        /// </summary>
        public const string G2SNamespace = "http://www.gamingstandards.com/g2s/schemas/v1.0.3";

        /// <summary>
        /// The root xml namespace used in the S2S protocol.
        /// </summary>
        public const string S2SNamespace = "http://www.gamingstandards.com/s2s/schemas/v1.2.6/";

        /// <summary>
        /// 
        /// </summary>
        public const string XlatNamespace = "http://xlat.igt.com/v1.0.0";

        /// <summary>
        /// The root xml namespace used in the S2S protocol for IGT customizations
        /// </summary>
        public const string IGTS2SNamespace = "http://s2s.igt.com/s2s/v1.0.0";

        /// <summary>
        /// The root xml namespace used in the G2S protocol for IGT customizations
        /// </summary>
        public const string IGTG2SNamespace = "http://g2s.igt.com/g2s/v1.0.0";

        /// <summary>
        /// 
        /// </summary>
        public static XmlType S2SXmlType;
        /// <summary>
        /// 
        /// </summary>
        public static XmlType G2SXmlType;

        /// <summary>
        /// 
        /// </summary>
        public static XmlType IGTS2SXmlType;
        /// <summary>
        /// 
        /// </summary>
        public static XmlType IGTG2SXmlType;

        /// <summary>
        /// A <see cref="System.Xml.XPath.XPathExpression">XPathExpression</see> component that will retrun all attributes for the currently selected element.
        /// </summary>
        public static XPathExpression GetAllAttributesXPathExpression = XPathExpression.Compile( "@*" );
        /// <summary>
        /// A <see cref="System.Xml.XPath.XPathExpression">XPathExpression</see> component that will retrun all child elements for the currently selected element.
        /// </summary>
        public static XPathExpression GetAllElementsXPathExpression = XPathExpression.Compile( "*" );

        private static IDictionary<string, string> _xmlPrefixes = new Dictionary<string, string>();

        /// <summary>
        /// Initializes the <see cref="XmlLib"/> class.
        /// </summary>
        static XmlLib()
        {
            _xmlPrefixes.Clear();

            _xmlPrefixes.Add( XmlPrefix, XmlPrefix );

            _xmlPrefixes.Add( G2SNamespace, G2SPrefix );
            _xmlPrefixes.Add( S2SNamespace, S2SPrefix );
            _xmlPrefixes.Add( IGTS2SNamespace, IGTPrefix );
            _xmlPrefixes.Add( IGTG2SNamespace, IGTPrefix );
            _xmlPrefixes.Add( "http://www.gamingstandards.com/g2s/schemas/v1.0.3/Ext1", "g2s1" );
            _xmlPrefixes.Add( "http://www.gamingstandards.com/s2s/schemas/v1.2.6/Ext1", "s2s1" );
            _xmlPrefixes.Add( "http://www.gamingstandards.com/s2s/schemas/v1.2.6/Ext2", "s2s2" );
            _xmlPrefixes.Add( "http://s2s.igt.com", "s2sIgt" );
            _xmlPrefixes.Add( "http://ext.gamingstandards.com/s2s/schemas/v1.0.0/", "gat" );
            _xmlPrefixes.Add( "http://s2s.igt.com/bonus/v1.0.0", "bns" );
            _xmlPrefixes.Add( "http://s2s.igt.com/mediaEvent/v1.0.0", "media" );
            _xmlPrefixes.Add( "http://s2s.igt.com/printer/v1.0.0", "prn" );
            _xmlPrefixes.Add( "http://s2s.igt.com/inbox/v1.0.0", "inbox" );
            _xmlPrefixes.Add( "http://www.igt.com/realtime/v1.0.0", "rt" );
            _xmlPrefixes.Add( "http://www.igt.com/scard/v1.0.0", "sc" );
            _xmlPrefixes.Add( "http://s2s.igt.com/comms/v1.0.0", "com" );
            _xmlPrefixes.Add( "http://s2s.igt.com/accountingMeter/v1.0.0", "am" );
            _xmlPrefixes.Add( "http://s2s.igt.com/voucher/v1.0.0", "vch" );
            _xmlPrefixes.Add( "http://www.igt.com/watext/v1.0.0", "wat_ext" );
            _xmlPrefixes.Add( "http://www.igt.com/watext2/v1.0.0", "wat_ext2" );
            _xmlPrefixes.Add( "http://s2s.igt.com/player/v1.0.1", "plr_1" );
            _xmlPrefixes.Add( "http://s2s.igt.com/voucher/v1.0.1", "vch" );
            _xmlPrefixes.Add( "http://s2s.igt.com/player/v1.0.2", "plr" );
            _xmlPrefixes.Add( "http://s2s.igt.com/infoUpdate/v1.0.0", "ifo" );
            _xmlPrefixes.Add( "http://s2s.igt.com/clientRegistration-ext1/v1.0.0", "reg" );
            _xmlPrefixes.Add( "http://s2s.igt.com/event-ext1/v1.0.0", "evt_ext1" );
            _xmlPrefixes.Add( "http://www.igt.com/authExt1/v1.0.0", "auth_ext1" );
            _xmlPrefixes.Add( "http://s2s.igt.com/player-ext1/v1.0.0", "plr_ext1" );
            _xmlPrefixes.Add( "http://s2s.igt.com/handpay-ext1/v1.0.0", "hpy" );

            _xmlPrefixes.Add( "http://www.igt.com/kgi/v1.0.0/", "kgi" );

            S2SXmlType = new XmlType( S2SPrefix, S2SNamespace );
            G2SXmlType = new XmlType( G2SPrefix, G2SNamespace );

            IGTS2SXmlType = new XmlType( IGTPrefix, IGTS2SNamespace );
            IGTG2SXmlType = new XmlType( IGTPrefix, IGTG2SNamespace );
        }

        /// <summary>
        /// Gets the XML prefix from a known xml namespace.
        /// </summary>
        /// <param name="ns">The ns.</param>
        /// <returns></returns>
        public static string GetXmlPrefixFromNamespace( string ns )
        {
            if ( string.IsNullOrEmpty( ns ) )
            {
                return IGTPrefix;
            }

            if ( _xmlPrefixes.ContainsKey( ns ) )
            {
                return _xmlPrefixes[ ns ];
            }
            else
            {
                return IGTPrefix;
            }
        }

        /// <summary>
        /// Determines whether the specified x nav has attribute.
        /// </summary>
        /// <param name="xNav">The x nav.</param>
        /// <param name="attr">The attr.</param>
        /// <returns>
        /// 	<c>true</c> if the specified x nav has attribute; otherwise, <c>false</c>.
        /// </returns>
        public static bool HasAttribute( XPathNavigator xNav, string attr )
        {
            return HasAttribute( xNav, attr, XmlNamespace, string.Empty );
        }

        /// <summary>
        /// Determines whether the specified x nav has attribute.
        /// </summary>
        /// <param name="xNav">The x nav.</param>
        /// <param name="attr">The attr.</param>
        /// <param name="ns">The ns.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <returns>
        /// 	<c>true</c> if the specified x nav has attribute; otherwise, <c>false</c>.
        /// </returns>
        public static bool HasAttribute( XPathNavigator xNav, string attr, string ns, string defaultValue )
        {
            return !string.IsNullOrEmpty( GetAttributeValue( xNav, attr, ns, defaultValue ) );
        }

        /// <summary>
        /// Gets the attribute value.
        /// </summary>
        /// <param name="node">The node.</param>
        /// <param name="attr">The attr.</param>
        /// <param name="ns">The ns.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <returns></returns>
        public static string GetAttributeValue( XmlNode node, string attr, string ns, string defaultValue )
        {
            return GetAttributeValue<string>( node, attr, ns, defaultValue );
        }

        /// <summary>
        /// Gets the attribute value.
        /// </summary>
        /// <param name="xmlReader">The XML reader.</param>
        /// <param name="attr">The attr.</param>
        /// <param name="ns">The ns.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <returns></returns>
        public static string GetAttributeValue( XmlReader xmlReader, string attr, string ns, string defaultValue )
        {
            return GetAttributeValue<string>( xmlReader, attr, ns, defaultValue );
        }

        /// <summary>
        /// Gets the attribute value.
        /// </summary>
        /// <param name="xNav">The x nav.</param>
        /// <param name="attr">The attr.</param>
        /// <param name="ns">The ns.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <returns></returns>
        public static string GetAttributeValue( XPathNavigator xNav, string attr, string ns, string defaultValue )
        {
            return GetAttributeValue<string>( xNav, attr, ns, defaultValue );
        }

        /// <summary>
        /// Gets the attribute value.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="xmlNode">The XML node.</param>
        /// <param name="attr">The attr.</param>
        /// <param name="ns">The ns.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <returns></returns>
        public static T GetAttributeValue<T>( XmlNode xmlNode, string attr, string ns, T defaultValue )
        {
            // HACK: Try different versions of the string to find a value.  Xml is case-sensitive
            string v = xmlNode.Attributes[ attr ] != null ? xmlNode.Attributes[ attr ].Value : null;

            if ( string.IsNullOrEmpty( v ) )
            {
                v = xmlNode.Attributes[ attr.ToLower() ] != null ? xmlNode.Attributes[ attr.ToLower() ].Value : null;

                if ( string.IsNullOrEmpty( v ) )
                {
                    v = xmlNode.Attributes[ attr.ToUpper() ] != null ? xmlNode.Attributes[ attr.ToUpper() ].Value : null;
                }
            }

            if ( string.IsNullOrEmpty( v ) )
            {
                return defaultValue;
            }

            return TypeConverter.Convert( v, defaultValue );
        }

        /// <summary>
        /// Gets the attribute value.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="xmlReader">The XML reader.</param>
        /// <param name="attr">The attr.</param>
        /// <param name="ns">The ns.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <returns></returns>
        public static T GetAttributeValue<T>( XmlReader xmlReader, string attr, string ns, T defaultValue )
        {
            // HACK: Try different versions of the string to find a value.  Xml is case-sensitive
            string v;

            // It is possible to try to fetch the attribute value, giving the XmlReader already located
            // on the attribute node.  In this case, just fetch the local value.
            if (xmlReader.NodeType == XmlNodeType.Attribute && xmlReader.Name.EqualsIgnoreCase(attr))
            {
                v = xmlReader.Value;
            }
            else
            {
                v = xmlReader.GetAttribute(attr, ns);
            }

            if ( string.IsNullOrEmpty( v ) )
            {
                v = xmlReader.GetAttribute( attr.ToLower() );

                if ( string.IsNullOrEmpty( v ) )
                {
                    v = xmlReader.GetAttribute( attr.ToUpper() );
                }
            }

            if ( string.IsNullOrEmpty( v ) )
            {
                return defaultValue;
            }

            return TypeConverter.Convert( v, defaultValue );
        }

        /// <summary>
        /// Gets the attribute value.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="xNav">The x nav.</param>
        /// <param name="attr">The attr.</param>
        /// <param name="ns">The ns.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <returns></returns>
        public static T GetAttributeValue<T>( XPathNavigator xNav, string attr, string ns, T defaultValue )
        {
            // HACK: Try different versions of the string to find a value.  Xml is case-sensitive
            string v;

            // It is possible to try to fetch the attribute value, giving the XPathNavigator already located
            // on the attribute node.  In this case, just fetch the local value
            if (xNav.NodeType == XPathNodeType.Attribute && xNav.LocalName.EqualsIgnoreCase(attr))
            {
                v = xNav.Value;
            }
            else
            {
                v = xNav.GetAttribute(attr, ns);
            }

            if ( string.IsNullOrEmpty( v ) )
            {
                // Try with default xml namespace
                v = xNav.GetAttribute( attr, XmlNamespace );

                if ( string.IsNullOrEmpty( v ) )
                {
                    // Try with no namespace
                    v = xNav.GetAttribute( attr, string.Empty );

                    if ( string.IsNullOrEmpty( v ) )
                    {
                        v = xNav.GetAttribute( attr.ToLower(), string.Empty );

                        if ( string.IsNullOrEmpty( v ) )
                        {
                            v = xNav.GetAttribute( attr.ToUpper(), string.Empty );
                        }
                    }
                }
            }

            if ( string.IsNullOrEmpty( v ) )
            {
                return defaultValue;
            }

            return TypeConverter.Convert( v, defaultValue );
        }

        /// <summary>
        /// Gets the element value.
        /// </summary>
        /// <param name="xmlReader">The XML reader.</param>
        /// <param name="element">The element.</param>
        /// <returns></returns>
        public static string GetElementValue( XmlReader xmlReader, string element )
        {
            return GetElementValue<string>( xmlReader, element, string.Empty );
        }

        /// <summary>
        /// Gets the element value.
        /// </summary>
        /// <param name="xNav">The x nav.</param>
        /// <param name="element">The element.</param>
        /// <returns></returns>
        public static string GetElementValue( XPathNavigator xNav, string element )
        {
            return GetElementValue<string>( xNav, element, string.Empty );
        }

        /// <summary>
        /// Gets the element value.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="xmlReader">The XML reader.</param>
        /// <param name="element">The element.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <returns></returns>
        public static T GetElementValue<T>( XmlReader xmlReader, string element, T defaultValue )
        {
            if ( xmlReader == null )
            {
                throw new ArgumentNullException( "xmlReader" );
            }

            xmlReader.ReadStartElement( element );

            if ( !xmlReader.Name.Equals( element ) )
            {
                xmlReader.ReadStartElement( element.ToLower() );

                if ( !xmlReader.Name.Equals( element ) )
                {
                    xmlReader.ReadStartElement( element.ToUpper() );
                }
            }

            string elementValue = xmlReader.ReadString();

            xmlReader.ReadEndElement();

            if ( string.IsNullOrEmpty( elementValue ) )
            {
                return defaultValue;
            }

            return TypeConverter.Convert( elementValue, defaultValue );
        }

        /// <summary>
        /// Gets the element value.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="xNav">The x nav.</param>
        /// <param name="element">The element.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <returns></returns>
        public static T GetElementValue<T>( XPathNavigator xNav, string element, T defaultValue )
        {
            return GetElementValue<T>( xNav, element, defaultValue, null );
        }

        /// <summary>
        /// Gets the element value.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="xNav">The x nav.</param>
        /// <param name="element">The element.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <param name="resolver">The resolver.</param>
        /// <returns></returns>
        public static T GetElementValue<T>( XPathNavigator xNav, string element, T defaultValue, IXmlNamespaceResolver resolver )
        {
            if ( xNav == null )
            {
                throw new ArgumentNullException( "xNav" );
            }

            XPathNavigator xElement = xNav.SelectSingleNode( element, resolver );

            // HACK: Try different versions of the string to find a value.  Xml is case-sensitive
            if ( xElement == null )
            {
                xElement = xNav.SelectSingleNode( element.ToLower() );

                if ( xElement == null )
                {
                    xElement = xNav.SelectSingleNode( element.ToUpper() );
                }
            }

            if ( xElement == null )
            {
                return defaultValue;
            }

            if ( string.IsNullOrEmpty( xElement.Value ) )
            {
                return defaultValue;
            }

            return TypeConverter.Convert( xElement.Value, defaultValue );
        }

        /// <summary>
        /// Gets the element value.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="xNav">The x nav.</param>
        /// <param name="expr">The expr.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <returns></returns>
        public static T GetElementValue<T>( XPathNavigator xNav, XPathExpression expr, T defaultValue )
        {
            XPathNavigator xElement = xNav.SelectSingleNode( expr );

            if ( string.IsNullOrEmpty( xElement.Value ) )
            {
                return defaultValue;
            }

            return TypeConverter.Convert( xElement.Value, defaultValue );
        }

        /// <summary>
        /// Will write an xml attribute using the <see cref="XmlWriter">XmlWriter</see> component of any type T.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="writer">The writer.</param>
        /// <param name="attributeName">Name of the attribute.</param>
        /// <param name="attributeValue">The attribute value.</param>
        public static void WriteAttributeString<T>( XmlWriter writer, string attributeName, T attributeValue )
        {
            WriteAttributeString(
                writer,
                attributeName,
                attributeValue,
                XmlPrefix,
                XmlNamespace
                );
        }

        /// <summary>
        /// Will write an xml attribute using the <see cref="XmlWriter">XmlWriter</see> component of any type T.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="writer">The writer.</param>
        /// <param name="attributeName">Name of the attribute.</param>
        /// <param name="attributeValue">The attribute value.</param>
        /// <param name="prefix">The prefix.</param>
        /// <param name="ns">The ns.</param>
        public static void WriteAttributeString<T>( XmlWriter writer, string attributeName, T attributeValue, string prefix, string ns )
        {
            if ( object.ReferenceEquals( attributeValue, null ) )
            {
                return;
            }

            string str = TypeConverter.Convert( attributeValue, string.Empty );

            writer.WriteAttributeString(
                prefix,
                attributeName,
                ns,
                str
                );
        }

        /// <summary>
        /// Writes the XML.
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <returns></returns>
        public static string WriteXml( IXmlSerialize obj )
        {
            return WriteXml( obj, false );
        }

        /// <summary>
        /// Writes the XML.
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <param name="indent">if set to <c>true</c> [indent].</param>
        /// <returns></returns>
        public static string WriteXml( IXmlSerialize obj, bool indent )
        {
            //
            StringBuilder builder = new StringBuilder( 1024 );

            //
            XmlWriterSettings xmlSettings = new XmlWriterSettings();
            xmlSettings.Encoding = Encoding.UTF8;
            xmlSettings.OmitXmlDeclaration = true;

            if ( indent )
            {
                xmlSettings.Indent = true;
                xmlSettings.IndentChars = "  ";
                xmlSettings.NewLineOnAttributes = true;
            }

            using ( XmlWriter writer = XmlTextWriter.Create( builder, xmlSettings ) )
            {
                obj.ToXml( writer );
            }

            return builder.ToString();
        }

    }
}
